//Copyright 2008 Marcos Boyington
//Simple class that globs up all translation from ResX -> .resources and source file

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Resources;
using System.IO;
using System.Xml;
using System.Reflection;
using System.ComponentModel;
using System.CodeDom;
using System.CodeDom.Compiler;
using DMKSoftware.CodeGenerators.Tools;

namespace ResXImproved
{
	public class ResXGenerator
	{
		private System.Web.Compilation.AssemblyBuilder _asmBuilder = null;
		private CodeDomProvider _domProvider = null;
		private string _Name;
		private bool _generateInternal = true;

		public ResXGenerator(System.Web.Compilation.AssemblyBuilder builder, string name)
		{
			_asmBuilder = builder;
			_domProvider = builder.CodeDomProvider;
			_Name = name;
		}

		public ResXGenerator(string name, CodeDomProvider provider)
		{
			_domProvider = provider;
			_Name = name;
		}

		public bool GenerateInternal
		{
			get
			{ return _generateInternal; }
			set
			{ _generateInternal = value; }
		}

		Dictionary<string, Assembly> _AssemblyLookup;
		private Type GetTypeFromAssembly (XmlAttribute typeAttribute)
		{
			if (typeAttribute == null)
				return typeof(string);
			string type = typeAttribute.Value;
			int assemblyTypeSplit = type.IndexOf(',');
			if (assemblyTypeSplit != -1)
			{
				string assemblyString = type.Substring(assemblyTypeSplit + 1).TrimStart();
				string typeName = type.Substring(0, assemblyTypeSplit).Trim();

				return _AssemblyLookup[assemblyString].GetType(typeName);
			}
			else
				return Type.GetType(type);
		}

		private object CreateGeneric(string type, params Type[] types)
		{
			int assemblyTypeSplit = type.IndexOf(',');

			Type generic;
			if (assemblyTypeSplit != -1)
			{
				string assemblyString = type.Substring(assemblyTypeSplit + 1).TrimStart();
				string typeName = type.Substring(0, assemblyTypeSplit).Trim() + "`" + types.Length;
				generic = _AssemblyLookup[assemblyString].GetType(typeName);
			}
			else
				generic = Type.GetType(type + "`" + types.Length);

			if (generic == null)
				throw new InvalidOperationException(String.Format("Could not create generic type {0}", type));
			return Activator.CreateInstance(generic.MakeGenericType(types));
		}

		public void GenerateResources (Stream inFile, ResourceWriter resWriter, out CodeCompileUnit codeCompile)
		{
			Dictionary<string, object> resourceDictionary = new Dictionary<string, object>();
			System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
			System.Runtime.Serialization.Formatters.Soap.SoapFormatter soapFormatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();

			XmlDocument doc = new XmlDocument();
			doc.Load(inFile);

			_AssemblyLookup = new Dictionary<string, Assembly>();
			XmlNodeList nodeList = doc.SelectNodes("//root/assembly");
			foreach (XmlNode n in nodeList)
			{
				string alias = n.Attributes["alias"].Value;
				Assembly asm = Assembly.Load(n.Attributes["name"].Value);
				if (_asmBuilder != null)
					_asmBuilder.AddAssemblyReference(asm);
				_AssemblyLookup[alias] = asm;
			}

			nodeList = doc.SelectNodes("//root/data");
			foreach (XmlNode n in nodeList)
			{
				XmlAttribute typeAttribute = n.Attributes["type"];
				XmlAttribute mimeTypeAttribute = n.Attributes["mimetype"];
				string resName = n.Attributes["name"].Value;
				if (mimeTypeAttribute != null)
				{
					string value = n.SelectSingleNode("value").InnerText.Trim();
					byte[] byteArray = System.Convert.FromBase64String(value);
					switch (mimeTypeAttribute.Value)
					{
						case "application/x-microsoft.net.object.binary.base64":
							resourceDictionary.Add(resName, binaryFormatter.Deserialize(new MemoryStream(byteArray)));
							break;
						case "application/x-microsoft.net.object.soap.base64":
							resourceDictionary.Add(resName, soapFormatter.Deserialize(new MemoryStream(byteArray)));
							break;
						case "application/x-microsoft.net.object.bytearray.base64":
							resourceDictionary.Add(resName, byteArray);
							break;
						default:
							throw new InvalidOperationException(String.Format("Unknown mimetype: {0}", mimeTypeAttribute.Value));
					}
				}
				else
				{
					XmlNode listNode = n.SelectSingleNode("list");
					XmlNode dictNode = n.SelectSingleNode("dictionary");
					XmlNode valueNode = n.SelectSingleNode("value");
					if (listNode != null)
					{
						Type valueType = GetTypeFromAssembly(listNode.Attributes["type"]);
						TypeConverter valueConv = TypeDescriptor.GetConverter( valueType );
						if (!valueConv.CanConvertFrom(typeof(string)))
							throw new InvalidOperationException(String.Format("Object of type {0} cannot be converted from string", valueType));

						IList list = (IList)CreateGeneric(typeAttribute.Value, valueType);
						foreach (XmlNode subN in listNode.SelectNodes("value"))
						{
							list.Add(valueConv.ConvertFromString(subN.InnerXml));
						}

						resourceDictionary.Add(resName, list);
					}
					else if (dictNode != null)
					{
						Type keyType = GetTypeFromAssembly(dictNode.Attributes["keytype"]);
						Type valueType = GetTypeFromAssembly(dictNode.Attributes["valuetype"]);
						TypeConverter keyConv = TypeDescriptor.GetConverter( keyType );
						TypeConverter valueConv = TypeDescriptor.GetConverter( valueType );

						if ((! valueConv.CanConvertFrom(typeof(string))) || (! keyConv.CanConvertFrom(typeof(string))))
							throw new InvalidOperationException(String.Format("Object of type {0} cannot be converted from string", valueType));

						IDictionary dict = (IDictionary)CreateGeneric(typeAttribute.Value, keyType, valueType);
						foreach (XmlNode subN in dictNode.SelectNodes("pair"))
						{
							dict.Add(keyConv.ConvertFromString(subN.Attributes["key"].Value), valueConv.ConvertFromString(subN.InnerXml));
						}

						resourceDictionary.Add(resName, dict);
					}
					else if (valueNode != null)
					{
						string value = n.SelectSingleNode("value").InnerXml.Trim();
						Type type = GetTypeFromAssembly(typeAttribute);
						TypeConverter conv = TypeDescriptor.GetConverter(type);

						if (!conv.CanConvertFrom(typeof(string)))
							throw new InvalidOperationException(String.Format("Object of type {0} cannot be converted from string", type));

						resourceDictionary.Add(resName, conv.ConvertFromString(value));
					}
					else
						throw new InvalidOperationException(String.Format("No data specified for resource: {0}", resName));
				}
			}

			foreach (KeyValuePair<string, object> pair in resourceDictionary)
			{
				if ( pair.Value.GetType() == typeof(byte[]) )
					resWriter.AddResource(pair.Key, (byte[])pair.Value);
				else
					resWriter.AddResource(pair.Key, pair.Value);
			}

			List<ResourceErrorData> unmatchableResources = new List<ResourceErrorData>();
			codeCompile = StronglyTypedResourceBuilderEx.Create(this.GetType(),
				resourceDictionary, _Name, "Resources",
				_domProvider, _generateInternal, unmatchableResources);
		}
	}
}
